Skip to content

Add ASN numbers as scan targets#2741

Merged
TheTechromancer merged 32 commits into3.0from
asn-as-targets
Mar 20, 2026
Merged

Add ASN numbers as scan targets#2741
TheTechromancer merged 32 commits into3.0from
asn-as-targets

Conversation

@liquidsec
Copy link
Collaborator

@liquidsec liquidsec commented Oct 17, 2025

Summary

  • ASN as targets: You can now pass ASN numbers (e.g. AS13335) as scan targets. They are automatically expanded into their constituent IP ranges before the scan begins.
  • Migrated to asndb library: ASN lookups now use the asndb library instead of hand-rolled API calls. This handles ASN→CIDR expansion, IP→ASN reverse lookups, and caching.
  • Migrated to radixtarget 4.x: BBOTTarget now uses composition (owns a RadixTarget) instead of inheritance. This aligns with the radixtarget 4.x string-only API.
  • Replaced SHA-1 hashing with seahash/FNV-1a: Target and blacklist hashing now uses faster 64-bit hash functions instead of SHA-1.
  • BBOTTarget.generate_children(): New async method on the target object that expands compound seed types (like ASNs) into child events before the scan begins.
  • shodan_enterprise module cleanup: Removed redundant ASN emission (handled by asn report module via asndb). Fixed scope filtering to use framework's in_scope_only attribute properly.
  • Engine subprocess leak fix: Fixed a bug where DNS/web engine subprocesses were not properly shut down, causing resource accumulation across tests.
  • install_core_deps performance: Core dependency installation now caches properly, reducing redundant 15s Ansible invocations to ~0s on subsequent runs.
  • Graceful handling for missing module deps: Modules with uninstallable dependencies no longer crash the scan; they are skipped with a warning. Non-TTY sudo prompts are also handled gracefully.
  • Test fixes: Fixed several test reliability issues including test_python_api preset checks and scope accuracy tests. Added scope filtering tests for shodan_enterprise. Removed mocks in favor of real asndb lookups.

# Test ASN with other targets
target = BBOTTarget("ASN:15169", "evilcorp.com", "1.2.3.4/24")
assert "ASN:15169" in target.seeds.inputs
assert "evilcorp.com" in target.seeds.inputs

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization

The string [evilcorp.com](1) may be at an arbitrary position in the sanitized URL.

Copilot Autofix

AI 5 months ago

The code currently checks for the presence of "evilcorp.com" as a substring in target.seeds.inputs, which is subject to the vulnerabilities described. To fix this, the test should specifically check if "evilcorp.com" is present as an actual host, not as a substring within any input string. This can be done by parsing the inputs and extracting their hostnames for comparison, or, if target.seeds provides a .hosts attribute containing canonicalized hosts, use that for the assertion. Thus, change the assertion on line 377 to ensure "evilcorp.com" is in the normalized or parsed hosts. This may require referencing the hosts set or parsing the inputs using a standard library (e.g., urlparse).

Make the following changes in bbot/test/test_step_1/test_target.py:

  • On line 377, replace the substring-in check with an explicit hostname check using either the .hosts attribute (if available), or by parsing input values using urlparse.
  • If using urlparse, import it from the standard library.
Suggested changeset 1
bbot/test/test_step_1/test_target.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/bbot/test/test_step_1/test_target.py b/bbot/test/test_step_1/test_target.py
--- a/bbot/test/test_step_1/test_target.py
+++ b/bbot/test/test_step_1/test_target.py
@@ -374,7 +374,7 @@
     # Test ASN with other targets
     target = BBOTTarget("ASN:15169", "evilcorp.com", "1.2.3.4/24")
     assert "ASN:15169" in target.seeds.inputs
-    assert "evilcorp.com" in target.seeds.inputs
+    assert "evilcorp.com" in [str(host) for host in target.seeds.hosts]
     assert "1.2.3.0/24" in target.seeds.inputs  # IP ranges are normalized to network address
 
     # Test ASN targets must be expanded before being useful in whitelist/blacklist
EOF
@@ -374,7 +374,7 @@
# Test ASN with other targets
target = BBOTTarget("ASN:15169", "evilcorp.com", "1.2.3.4/24")
assert "ASN:15169" in target.seeds.inputs
assert "evilcorp.com" in target.seeds.inputs
assert "evilcorp.com" in [str(host) for host in target.seeds.hosts]
assert "1.2.3.0/24" in target.seeds.inputs # IP ranges are normalized to network address

# Test ASN targets must be expanded before being useful in whitelist/blacklist
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
@github-actions
Copy link
Contributor

github-actions bot commented Oct 17, 2025

📊 Performance Benchmark Report

Comparing 3.0 (baseline) vs asn-as-targets (current)

📈 Detailed Results (All Benchmarks)

📋 Complete results for all benchmarks - includes both significant and insignificant changes

🧪 Test Name 📏 Base 📏 Current 📈 Change 🎯 Status
Bloom Filter Dns Mutation Tracking Performance 3.96ms 3.93ms -0.8%
Bloom Filter Large Scale Dns Brute Force 18.34ms 17.99ms -1.9%
Large Closest Match Lookup 327.51ms 318.07ms -2.9%
Realistic Closest Match Workload 174.01ms 176.46ms +1.4%
Event Memory Medium Scan 1767 B/event 1774 B/event +0.4%
Event Memory Large Scan 1757 B/event 1757 B/event +0.0%
Event Validation Full Scan Startup Small Batch 463.62ms 365.12ms -21.2% 🟢🟢🟢 🚀
Event Validation Full Scan Startup Large Batch 712.42ms 492.09ms -30.9% 🟢🟢🟢 🚀
Make Event Autodetection Small 25.52ms 25.61ms +0.3%
Make Event Autodetection Large 262.33ms 264.17ms +0.7%
Make Event Explicit Types 11.39ms 11.35ms -0.3%
Excavate Single Thread Small 3.476s 3.356s -3.4%
Excavate Single Thread Large 9.228s 9.188s -0.4%
Excavate Parallel Tasks Small 3.605s 3.542s -1.8%
Excavate Parallel Tasks Large 6.952s 6.924s -0.4%
Is Ip Performance 2.88ms 2.93ms +1.4%
Make Ip Type Performance 10.62ms 10.61ms -0.1%
Mixed Ip Operations 4.14ms 4.18ms +1.0%
Typical Queue Shuffle 54.94µs 55.18µs +0.4%
Priority Queue Shuffle 593.96µs 601.62µs +1.3%

🎯 Performance Summary

+ 2 improvements 🚀
  18 unchanged ✅

🔍 Significant Changes (>10%)

  • Event Validation Full Scan Startup Small Batch: 21.2% 🚀 faster
  • Event Validation Full Scan Startup Large Batch: 30.9% 🚀 faster

🐍 Python Version 3.11.15

@codecov
Copy link

codecov bot commented Oct 17, 2025

Codecov Report

❌ Patch coverage is 88.45750% with 110 lines in your changes missing coverage. Please review.
✅ Project coverage is 91%. Comparing base (b90f1d3) to head (a294f89).
⚠️ Report is 10 commits behind head on 3.0.

Files with missing lines Patch % Lines
bbot/core/helpers/misc.py 57% 22 Missing ⚠️
bbot/core/helpers/asn.py 59% 18 Missing ⚠️
bbot/test/conftest.py 0% 16 Missing ⚠️
bbot/scanner/target.py 92% 10 Missing ⚠️
bbot/core/engine.py 56% 8 Missing ⚠️
bbot/core/helpers/simhash.py 86% 7 Missing ⚠️
bbot/core/helpers/depsinstaller/installer.py 45% 5 Missing ⚠️
bbot/modules/baddns.py 67% 5 Missing ⚠️
bbot/cli.py 88% 4 Missing ⚠️
bbot/scanner/scanner.py 94% 4 Missing ⚠️
... and 7 more
Additional details and impacted files
@@          Coverage Diff           @@
##             3.0   #2741    +/-   ##
======================================
- Coverage     92%     91%    -0%     
======================================
  Files        436     434     -2     
  Lines      36320   36452   +132     
======================================
+ Hits       33059   33141    +82     
- Misses      3261    3311    +50     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TheTechromancer
Copy link
Collaborator

This PR is currently waiting on two things:

@liquidsec liquidsec changed the base branch from dev to 3.0 February 26, 2026 23:12
@liquidsec liquidsec changed the title ASN Helper / Add ASN's as an available type type Add ASN numbers as scan targets Feb 28, 2026
async def as_completed(coros):
async def as_completed(
coroutines: Iterable[Awaitable],
max_concurrent: Optional[int] = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default limit should be something reasonable like 20-30 etc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this still applies correct?

Comment on lines +479 to +482
raise BBOTError(
log.warning(
f"Error loading module {module_name}: {e}. You may have leftover artifacts from an older version of BBOT. Try deleting/renaming your '~/.bbot' directory."
) from e
)
module = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error was intentional to stop the scan if a module failed to load

Comment on lines +293 to +304
if self._target is None:
raise ValueError("Cannot access target before preset is baked (use ._seeds instead)")
return self._target

@property
def seeds(self):
if self._target is None:
raise ValueError("Cannot access target before preset is baked (use ._seeds instead)")
return None
return self.target.seeds

@property
def blacklist(self):
if self._target is None:
raise ValueError("Cannot access blacklist before preset is baked (use ._blacklist instead)")
return None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these errors are designed to catch situations where these attributes are accessed prematurely (i.e. before bake). if they're returning None, we shouldn't need to access them

let's put it back and see what breaks

Comment on lines +1168 to +1221
j["target"] = self.preset.target.json
j["preset"] = self.preset.to_dict(redact_secrets=True)
if self.preset is not None:
j["target"] = self.preset.target.json
j["preset"] = self.preset.to_dict(redact_secrets=True)
else:
j["target"] = {}
j["preset"] = {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we try reverting this to see why it had to be introduced? a scan should never exist without a preset (it should be impossible)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after migrating to asndb pypi library, passing in helpers shouldn't be necessary

Copy link
Collaborator

@TheTechromancer TheTechromancer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asdf

kwargs["stdin"] = asyncio.subprocess.PIPE

log.hugeverbose(f"run: {' '.join(command)}")
log.debug(f"run: {' '.join(command)}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we must show commands when -v. if necessary, we can add a exclusion flag

Copy link
Collaborator

@TheTechromancer TheTechromancer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asdf

liquidsec added 15 commits March 5, 2026 15:01
- BaseTarget no longer subclasses RadixTarget; uses composition instead
- Rename strict_dns_scope -> strict_scope everywhere
- Update host_size_key import path for radixtarget 4.x
- Handle radixtarget 4.x API changes (strings-only, no _add, hash is int)
- Skip acl_mode when strict_scope is True (mutually exclusive in 4.x)
- Update test assertions for new hash values and string-based hosts

Work in progress - more test fixes needed.
# Conflicts:
#	bbot/scanner/scanner.py
#	bbot/test/test_step_1/test_python_api.py
…cope filtering

- Remove ASN event emission (already handled by asn report module via asndb)
- Replace scope_distance_modifier+filter_event with proper in_scope_only class attr
- Dynamically set scope_distance_modifier=1 when in_scope_only option is disabled
- Add tests for in_scope_only=True and in_scope_only=False behavior
Comment on lines +421 to +428
# After expanding seeds, update the target to include any new hosts from seed expansion
# This ensures that expanded targets (like IP ranges from ASN) are considered in-scope
# BUT only if no custom target was provided - don't override user's custom target
if not had_custom_target:
expanded_seed_hosts = set(self.seeds.hosts)
for host in expanded_seed_hosts:
if host not in self.target:
self.target.add(host)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't a target by nature custom? I.e. I don't think it's possible to specify ASNs without a custom target? It seems like this should always happen. What do you think

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the logic is correct (user can assign a more specific/explicit target than the seeds) but the naming made it confusing. Updated variable names/comments to alleviate.

Comment on lines +23 to +26
asn_helper = module_test.scan.helpers.asn
asn_helper._client = AsyncMock()
asn_helper._client.lookup_ip = AsyncMock(return_value=self.asndb_response)
asn_helper._client.lookup_asn = AsyncMock(return_value=self.asndb_response)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using asyncmock, we should use the real thing. we can hardcode 1.1.1.1 and 8.8.8.8, etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but the problem is the google ASN is ENORMOUS and the test takes too long and times out

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the purpose of the test is to test the module - not the asn behavior itself

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nevermind there was a big in the tests which i fixed. removed the mock

Comment on lines +346 to +364
# Ensure stdout/stderr are blocking before pytest writes summaries
try:
import sys
import fcntl
import os
import io

fds = []
for stream in (sys.stdout, sys.stderr):
try:
fds.append(stream.fileno())
except io.UnsupportedOperation:
pass
for fd in fds:
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
except Exception:
pass

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this to address a specific bug?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixes a bug where pytest crashes during test summary output with BlockingIOError: [Errno 11] write could not complete without blocking.

From claude:

 BBOT's engine subprocesses set stdout/stderr to non-blocking mode (via asyncio). If a subprocess doesn't clean up properly on shutdown, the file descriptors stay non-blocking. When pytest then tries to write its   
  summary (which can be large), the write fails because the fd can't accept all the data at once in non-blocking mode.
                                                                                                                                                                                                                        
  It was showing up as flaky test failures where all tests pass but pytest itself crashes at the end trying to print results.                                                                                           
   

@TheTechromancer TheTechromancer merged commit 08942f4 into 3.0 Mar 20, 2026
2 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants